import {celebrate, Joi} from "celebrate";
import {withLock} from "easylock";
import {Request, Router} from "express"
import ms = require("ms");
import {InstanceType} from "typegoose"
import {alipayCashier} from "../alipay/payment";
import {AddressModel} from "../database/models/address";
import {DeliveryModel} from "../database/models/delivery";
import {DeliveryOrderModel} from "../database/models/deliveryOrder";
import {GamePrize, GamePrizeModel} from "../database/models/gamePrize";
import {LipstickSeries, LipstickSeriesModel} from "../database/models/lipstickSeries";
import {Player, PlayerModel} from "../database/models/player";
import {Product, ProductModel} from "../database/models/Product";
import {wechatPayCashier} from "../wechat/wechatCashier";
import {InternalError} from "./error";
import {Response, withGamePrize, withLipstickSeries, withProduct, withUser} from "./middlewareType";
import {jwtAuthMiddleware} from "./passort";
import {getBroadcastInfo, getReadBroadcastPlayer, recordReadBroadcastPlayer} from './utils'

const prizeRouter = Router()

export default prizeRouter

prizeRouter.get('/',
  jwtAuthMiddleware,
  async (req: Request & withUser, res: Response) => {
    try {
      const prizes = await GamePrizeModel.find({
        player: req.user.id
      }).lean()

      for (const prize of prizes as Array<InstanceType<GamePrize>>) {
        if (prize.selectState !== `waitSelect`)
          continue

        const nowProduct = prize.product
        const p = await ProductModel.findById(prize.product)
        if (p)
          prize[`marketPrize`] = p.marketPrice
        else
          console.log(__filename, `miss product, id:${prize.product}`)

        const selectList = []
        if (!prize || !prize.lipstickSeries)
          continue

        const lipstickSeries: InstanceType<LipstickSeries> =
          await LipstickSeriesModel.findById(prize.lipstickSeries)
        if (!lipstickSeries || !lipstickSeries.products)
          continue

        for (const it of lipstickSeries.products) {
          const product = await ProductModel.findById(it).lean()
          if (!product)
            continue

          product[`selected`] = it.toString() === nowProduct.toString()
          selectList.push(product)
        }

        prize['selectList'] = selectList
      }

      res.json({
        ok: true,
        data: {prizes}
      })
    } catch (e) {
      console.log(e)
    }
  })

prizeRouter.post('/select/:prizeId',
  jwtAuthMiddleware,
  celebrate(
    {
      params: Joi.object().keys({prizeId: Joi.string().required()}),
      body: Joi.object().keys({
        productId: Joi.string().required()
      })
    }
  ),
  async (req: Request & withGamePrize & withLipstickSeries & withProduct, res: Response, next) => {
    const prize = await GamePrizeModel.findById(req.params.prizeId)

    if (!prize)
      return res.error(InternalError("NO_SUCH_PRIZE"))

    if (prize.selectState !== 'waitSelect')
      return res.error(InternalError("ALREADY_SELECTED_PRIZE"))

    req.gamePrize = prize

    if (!prize.lipstickSeries)
      return res.error(InternalError("FAKE_CLIENT"))

    const ls = await LipstickSeriesModel.findById(req.gamePrize.lipstickSeries)
      .populate('products')
    if (!ls)
      return res.error(InternalError("NO_SUCH_SERIES"))
    req.lipstickSeries = ls

    const p = await ProductModel.findById(req.body.productId)
    if (!p)
      return res.error(InternalError("NO_SUCH_PRODUCT"))

    if (p.state !== 'on')
      return res.error(InternalError("NO_ON_STOCK"))

    req.product = p
    next()
  },
  async (req: Request & withUser & withGamePrize & withLipstickSeries & withProduct, res: Response, next) => {
    try {
      const prize =
        await withLock(req.user.id, async () => {

          if (req.body.productId.toString() === req.gamePrize.product.toString()) {
            return await GamePrizeModel.findByIdAndUpdate(
              {_id: req.params.prizeId},
              {$set: {selectState: 'selected'}},
              {upsert: false, new: true})
          }

          const products = req.lipstickSeries.products as Array<InstanceType<Product>>
          const destProduct = products.find((v, i, a) => {
            return v._id.toString() === req.product._id.toString()
          })

          if (!destProduct)
            throw InternalError("NO_SUCH_PRODUCT")

          const p = await withLock(destProduct.id, async () => {
            return await ProductModel.findByIdAndUpdate(
              {_id: destProduct._id, onStock: true},
              {$inc: {stock: -1}},
              {upsert: false, new: true})
          })

          if (!p)
            throw InternalError("NO_ON_STOCK")

          return await GamePrizeModel.findByIdAndUpdate(
            {_id: req.params.prizeId},
            {
              $set:
                {
                  selectState: 'selected',
                  product: destProduct._id,
                  productName: destProduct.name,
                  productDescription: destProduct.description,
                  productUrl: destProduct.coverUrl,
                }
            },
            {upsert: false, new: true})
        })
      res.json({
        ok: true,
        data: {prize}
      })

    } catch (e) {
      res.error(e)
    }
  })

prizeRouter.post('/deliver/fee',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      prizeIds: Joi.array().items(Joi.string().required()).required()
    })
  }),
  async (req: Request & withUser, res: Response) => {
    try {

      await withLock(req.user.id, async () => {

        const prizes = []
        let sumPrizePrice = 0

        for (const prizeId of req.body.prizeIds) {
          const prize: InstanceType<GamePrize> = await GamePrizeModel.findOne({
            _id: prizeId,
            player: req.user.id
          })

          if (!prize) {
            throw InternalError('NO_SUCH_PRIZE')
          }

          if (prize.state === 'idle') {
            prizes.push(prize)
          } else {
            throw InternalError('ALREADY_IN_DELIVERY')
          }

          const p: InstanceType<Product> = await ProductModel.findOne({
            _id: prize.product
          })
          if (!p)
            throw InternalError("NO_SUCH_PRODUCT")
          sumPrizePrice += p.marketPrice
        }

        const fee = sumPrizePrice >= DEFAULT_DELIVERY_FEE_THRESHOLD ? 0 : DEFAULT_DELIVERY_FEE

        return res.json({
          ok: true,
          data: {
            fee
          }
        })

      })
    } catch (e) {
      res.error(e)
    }

  })

prizeRouter.post('/deliver/delivery',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      toAddress: Joi.string().required(),
      prizeIds: Joi.array().items(Joi.string().required()).required(),
      payType: Joi.string().valid(['wechatPay', 'aliPay'])
    })
  }),
  async (req: Request & withUser, res: Response) => {
    try {

      await withLock(req.user.id, async () => {

        const hash = {};
        for (const it of req.body.prizeIds) {
          if (hash[it]) {
            throw InternalError('FAKE_CLIENT')
          }
          hash[it] = true;
        }

        const address = await AddressModel.findOne({
          _id: req.body.toAddress, player: req.user.id
        })

        if (!address) {
          throw InternalError('NO_SUCH_ADDRESS')
        }

        const prizes = []
        let sumPrizePrice = 0

        for (const prizeId of req.body.prizeIds) {
          const prize: InstanceType<GamePrize> = await GamePrizeModel.findOne({
            _id: prizeId,
            player: req.user.id
          })

          if (!prize)
            throw InternalError('NO_SUCH_PRIZE')

          if (prize.state === 'idle')
            prizes.push(prize)
          else if (prize.state === 'prepared')
            throw InternalError("ALREADY_ADD_DELIVERY")
          else
            throw InternalError('ALREADY_IN_DELIVERY')

          const p: InstanceType<Product> = await ProductModel.findOne({
            _id: prize.product
          })
          if (!p)
            throw InternalError("NO_SUCH_PRODUCT")
          sumPrizePrice += p.marketPrice
        }

        const fee = sumPrizePrice >= DEFAULT_DELIVERY_FEE_THRESHOLD ? 0 : DEFAULT_DELIVERY_FEE

        if (fee === 0) {
          for (const iterator of prizes) {
            iterator.state = 'prepared'
            iterator.requestDeliveryAt = new Date()
            iterator.toAddress = address
            await iterator.save()
          }
          const delivery = new DeliveryModel({
            player: req.user.id,
            prizes,
            toAddress: req.body.toAddress,
            fee,
            state: `prepared`
          })
          await delivery.save()

          return res.json({
            ok: true,
            data: {
              delivery
            }
          })
        } else {
          const dO = new DeliveryOrderModel({
            player: req.user.id,
            state: 'unpaid',
            prizes,
            toAddress: req.body.toAddress,
            RMBCost: fee,
            createAt: new Date()
          })
          await dO.save()

          const paymentArg = {
            title: '支付运费',
            detail: `${dO._id}`,
            price: fee,
            order: dO.id,
            type: 'DeliveryOrder',
            fromIP: req.ip
          }
          if (!req.body.payType)
            throw InternalError("NO_PAYTYPE_SELECTED")
          const cashier = req.body.payType === 'wechatPay' ? wechatPayCashier : alipayCashier

          const payOrder = await cashier.createPaymentOrder(paymentArg, ms('15min'))

          return res.json({
            ok: true,
            data: {
              deliveryOrder: dO,
              payOrder: payOrder.prepay
            }
          })
        }
      })
    } catch (e) {
      res.error(e)
    }

  })

prizeRouter.post('/return/point',
  jwtAuthMiddleware,
  celebrate({
    body: Joi.object().keys({
      prizeIds: Joi.array().items(Joi.string().required()).required()
    })
  }),
  async (req: Request & withUser, res: Response) => {
    try {

      await withLock(req.user.id, async () => {

        const hash = {};
        for (const it of req.body.prizeIds) {
          if (hash[it]) {
            throw InternalError('FAKE_CLIENT')
          }
          hash[it] = true;
        }

        const prizes = []
        let points = 0

        for (const prizeId of req.body.prizeIds) {
          const prize: InstanceType<GamePrize> = await GamePrizeModel.findOne({
            _id: prizeId,
            player: req.user.id
          })

          if (!prize)
            throw InternalError('NO_SUCH_PRIZE')

          if (prize.state === 'idle') {
            prizes.push(prize)
          } else if (prize.state === 'prepared') {
            throw InternalError("ALREADY_ADD_DELIVERY")
          } else {
            throw InternalError('ALREADY_IN_DELIVERY')
          }

          const p: InstanceType<Product> = await ProductModel.findOne({
            _id: prize.product
          })
          if (!p)
            throw InternalError("NO_SUCH_PRODUCT")

          points += p.pointRedeem
        }

        for (const iterator of prizes) {
          iterator.state = 'returned'
          await ProductModel.findByIdAndUpdate(iterator.product, {$inc: {stock: 1}})
          await iterator.save()
        }
        await PlayerModel.findByIdAndUpdate(req.user.id, {$inc: {point: points}})

        return res.json({
          ok: true,
          data: {
            points
          }
        })
      })
    } catch (e) {
      res.error(e)
    }

  })


prizeRouter.get('/broadcast',
  jwtAuthMiddleware,
  async (req: Request & withUser, res: Response) => {
    const broadcastData = await getBroadcastInfo()
    const startIndex = await getReadBroadcastPlayer(req.user._id.toString())
    const allLength = broadcastData.length
    await recordReadBroadcastPlayer(req.user._id.toString(), broadcastData.length)

    res.json({
      ok: true,
      data: {broadcastData: broadcastData.splice(startIndex,allLength)}
    })

  }
  )

export const DEFAULT_DELIVERY_FEE_THRESHOLD = 100
export const DEFAULT_DELIVERY_FEE = 12
